#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <unistd.h>
#include <signal.h>

#include "imbus_internal.h"
#include "im_io_unix.h"
#include "im_hash.h"

typedef struct _UnixMmappedInfo
{
    IMUInt64 offset;
    IMSize   length;
} UnixMmappedInfo;

struct _IMIOChannelUnix
{
    IMIOChannel channel;

    /* Private data */
    int fd;
    IMHashTable *mmapped_pointers;
};

struct _IMIOChannelUnixClass
{
    IMIOChannelClass parent_class;
};

static IMPointer
__new_unix_mmapped_info(IMUInt64 offset, IMSize length)
{
    IMPointer info = im_slice_alloc (sizeof(UnixMmappedInfo));
    _im_assert(info);
    ((UnixMmappedInfo*)info)->offset = offset;
    ((UnixMmappedInfo*)info)->length = length;
    return info;
}

static void
__free_unix_mmapped_info(IMPointer info)
{
    im_slice_free(sizeof(UnixMmappedInfo), info);
}

static void
__im_fd_set_close_on_exec (int fd)
{
    int val;

    val = fcntl (fd, F_GETFD, 0);

    if (val < 0)
        return;

    val |= FD_CLOEXEC;

   fcntl (fd, F_SETFD, val);
}

static IMBool
__im_io_channel_unix_valid       (IMIOChannel    *channel)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    int fcntl_flags;
    _im_assert (unix_channel);

    if (unix_channel->fd < 0)
        return FALSE;

    fcntl_flags = fcntl (unix_channel->fd, F_GETFL);
    return fcntl_flags != -1;
}

static IMBool
__im_io_channel_unix_read       (IMIOChannel    *channel,
                                 IMChar         *buf,
                                 IMSize          count,
                                 IMSize         *bytes_read,
                                 IMIOError      *error)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    ssize_t result;

    _im_assert (unix_channel);

    if (count > SSIZE_MAX) count = SSIZE_MAX;
    if (bytes_read) *bytes_read = 0;

    while (1) {
        result = read (unix_channel->fd, buf, count);
        /* Read success, just break */
        if (result >= 0) break;
#ifdef EINTR
        /* Interrupted? continue to try */
        if (errno == EINTR)
            continue;
#endif
        if (error)
            *error = im_io_channel_error_from_errno (errno);
        break;
    }
    if (bytes_read && result > 0) *bytes_read = result;
    return result > 0;
}

static IMBool
__im_io_channel_unix_write      (IMIOChannel    *channel,
                                 const IMChar   *buf,
                                 IMSize          count,
                                 IMSize         *bytes_written,
                                 IMIOError      *error)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    ssize_t result;
    void (*orig_sigpipe)(int);

    _im_assert (unix_channel);

    if (count > SSIZE_MAX) count = SSIZE_MAX;
    if (bytes_written) *bytes_written = 0;

    orig_sigpipe = signal (SIGPIPE, SIG_IGN);
    while (1) {
        result = write (unix_channel->fd, buf, count);
        /* Write success, just break */
        if (result >= 0) break;
#ifdef EINTR
        /* Interrupted? continue to try */
        if (errno == EINTR)
            continue;
#endif
        if (error)
            *error = im_io_channel_error_from_errno (errno);
        break;
    }
    if (bytes_written && result > 0) *bytes_written = result;

    if (orig_sigpipe != SIG_ERR)
        signal (SIGPIPE, orig_sigpipe);
    else
        signal (SIGPIPE, SIG_DFL);

    return result > 0;
}

static IMBool
__im_io_channel_unix_seek       (IMIOChannel    *channel,
                                 IMInt64         offset,
                                 IMIOSeekType    type,
                                 IMIOError      *error)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    int whence;
    off_t tmp_offset;
    off_t result;

    _im_assert (unix_channel);

    switch (type) {
        case IM_IO_SEEK_SET:
            whence = SEEK_SET;
            break;
        case IM_IO_SEEK_CUR:
            whence = SEEK_CUR;
            break;
        case IM_IO_SEEK_END:
            whence = SEEK_END;
            break;
        default:
            whence = -1;
            _im_assert_not_reached("Unknown seek type");
    }

    tmp_offset = offset;
    if (tmp_offset != offset) {
        if (error) *error = IM_IO_ERROR_INVAL;
        return FALSE;
    }

    result = lseek (unix_channel->fd, tmp_offset, whence);

    if (result < 0) {
        if (error) *error = im_io_channel_error_from_errno(errno);
        return FALSE;
    }
    return TRUE;
}

static IMBool
__im_io_channel_unix_tell       (IMIOChannel    *channel,
                                 IMUInt64       *offset,
                                 IMIOError      *error)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    off_t result;

    _im_assert (unix_channel);

    result = lseek (unix_channel->fd, 0, SEEK_CUR);

    if (result < 0) {
        if (error) *error = im_io_channel_error_from_errno(errno);
        return FALSE;
    }

    if (offset) *offset = result;
    return TRUE;
}

static IMBool
__im_io_channel_unix_close      (IMIOChannel    *channel,
                                 IMIOError      *error)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    if (close (unix_channel->fd) < 0) {
        if (error) *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }

    unix_channel->fd = -1;
    return TRUE;
}

static IMPointer
__im_io_channel_unix_mmap       (IMIOChannel    *channel,
                                 IMUInt64        offset,
                                 IMSize          length,
                                 IMBool          writeable,
                                 IMBool          shared,
                                 IMIOError      *error)
{
    IMInt pagesize;
    IMUInt64 delta_offset;
    IMPointer result;
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    pagesize = sysconf (_SC_PAGESIZE);
    if (pagesize > 1) {
        delta_offset = offset % ((IMUInt64)pagesize);
        offset = (offset / ((IMUInt64)pagesize)) * ((IMUInt64)pagesize);
    } else {
        delta_offset = offset;
        offset = 0;
    }

    result = mmap (0, length+delta_offset, PROT_READ | (writeable ? PROT_WRITE : 0),
                   (shared ? MAP_SHARED : MAP_PRIVATE),
                   unix_channel->fd, offset);

    if (result == MAP_FAILED) {
        if (error) *error = im_io_channel_error_from_errno (errno); 
        return 0;
    }
    result += delta_offset;

    if (!unix_channel->mmapped_pointers) {
        unix_channel->mmapped_pointers =
            im_hash_table_new_full (im_direct_hash,
                                    im_direct_equal,
                                    NULL,
                                    __free_unix_mmapped_info);
    }

    im_hash_table_insert (unix_channel->mmapped_pointers,
                          result,
                          __new_unix_mmapped_info(delta_offset, length)); 

    return result;
}

static IMBool
__im_io_channel_unix_munmap     (IMIOChannel    *channel,
                                 IMPointer       address,
                                 IMSize          length,
                                 IMIOError      *error)
{
    IMPointer key;
    IMPointer val;
    UnixMmappedInfo info;

    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    if (!unix_channel->mmapped_pointers ||
        !im_hash_table_lookup_extended (unix_channel->mmapped_pointers, address, &key, &val)) {
        if (error) *error = IM_IO_ERROR_INVAL;
        return FALSE;
    }

    info = *((UnixMmappedInfo*)val);
    im_hash_table_remove (unix_channel->mmapped_pointers, key);

    if (key != address || length != info.length) {
        if (error) *error = IM_IO_ERROR_INVAL;
        return FALSE;
    }

    address  = key - info.offset;
    length   = info.length+info.offset;
    if (munmap (address, length) != 0) {
        if (error) *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }
    return TRUE;
}

static IMBool
__im_io_channel_unix_set_flags  (IMIOChannel    *channel,
                                 IMUInt          flags,
                                 IMIOError      *error)
{
    long fcntl_flags;
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);
    fcntl_flags = 0;

    if (flags & IM_IO_FLAG_APPEND)
        fcntl_flags |= O_APPEND;
    if (flags & IM_IO_FLAG_NONBLOCK)
#ifdef O_NONBLOCK
        fcntl_flags |= O_NONBLOCK;
#else
        fcntl_flags |= O_NDELAY;
#endif

    if (fcntl (unix_channel->fd, F_SETFL, fcntl_flags) == -1) {
        if (error) *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }
    return TRUE;
}

static IMBool
__im_io_channel_unix_get_flags  (IMIOChannel    *channel,
                                 IMUInt         *flags,
                                 IMIOError      *error)
{
    int fcntl_flags;
    struct stat fstat_result;
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    *flags = 0;

    fcntl_flags = fcntl (unix_channel->fd, F_GETFL);

    if (fcntl_flags == -1) {
        if (error) *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }

    if (fstat (unix_channel->fd, &fstat_result) == -1) {
        if (error) *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }

    if (fcntl_flags & O_APPEND)
        *flags |= IM_IO_FLAG_APPEND;
#ifdef O_NONBLOCK
    if (fcntl_flags & O_NONBLOCK)
#else
    if (fcntl_flags & O_NDELAY)
#endif
        *flags |= IM_IO_FLAG_NONBLOCK;

    switch (fcntl_flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
        case O_RDONLY:
            *flags |= IM_IO_FLAG_IS_READABLE;
            break;
        case O_WRONLY:
            *flags |= IM_IO_FLAG_IS_WRITEABLE;
            break;
        case O_RDWR:
            *flags |= IM_IO_FLAG_IS_READABLE;
            *flags |= IM_IO_FLAG_IS_WRITEABLE;
            break;
        default:
            _im_assert_not_reached ("Invalid fcntl flags.\n");
            return FALSE;
    }

    if (S_ISREG(fstat_result.st_mode) || S_ISBLK(fstat_result.st_mode) ||
        S_ISCHR(fstat_result.st_mode)) {
        *flags |= IM_IO_FLAG_IS_SEEKABLE;
        *flags |= IM_IO_FLAG_IS_MMAPABLE;
    }
    return TRUE;
}

#define IO_UNIX_WAIT_READ  1
#define IO_UNIX_WAIT_WRITE 2
static IMBool
__im_io_unix_wait_internal (IMInt           fd,
                            IMInt           mode,
                            IMInt          *timeout,
                            IMIOError      *error)
{
    fd_set fds;
    int ret;
    struct timeval tv;
    IMUInt64 begin;

    _im_assert (fd < FD_SETSIZE);

    if (*timeout >= 0) {
        begin = im_get_current_time_in_milliseconds ();
        tv.tv_sec = *timeout / 1000;
        tv.tv_usec = (*timeout % 1000) * 1000;
    }

    while (1) {
        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        if (mode == IO_UNIX_WAIT_READ)
            ret = select(fd + 1,
                         &fds, NULL, NULL,
                         (*timeout >= 0 ? &tv : NULL));
        else if (mode == IO_UNIX_WAIT_WRITE)
            ret = select(fd + 1,
                         NULL, &fds, NULL,
                         (*timeout >= 0 ? &tv : NULL));
        else
            _im_assert_not_reached("Shouldn't go here.");

        if (*timeout > 0) {
            IMInt elapsed;
            IMUInt64 cur = im_get_current_time_in_milliseconds ();
            elapsed = (IMInt)(cur - begin);
            (*timeout) -= elapsed;
            if (*timeout > 0) {
                tv.tv_sec = *timeout / 1000;
                tv.tv_usec = (*timeout % 1000) * 1000;
            } else {
                tv.tv_sec = 0;
                tv.tv_usec = 0;
                *timeout = 0;
            }
        }

        if (ret > 0) {
            return TRUE;
        } else if (ret == 0) {
            if (*timeout == 0) {
                /* Set error to AGAIN to indicate that the resource is not
                 * avaliable yet. */
                if (error) *error = IM_IO_ERROR_AGAIN;
                return FALSE;
            } else {
                continue;
            }
        }

#ifdef EINTR
        /* Interrupted? continue to try */
        if (errno == EINTR)
            continue;
#endif
        if (error)
            *error = im_io_channel_error_from_errno (errno);
        return FALSE;
    }
    _im_assert_not_reached("Shouldn't go here.");
}

static IMBool
__im_io_unix_wait_for_readable (IMIOChannel    *channel,
                                IMInt          *timeout,
                                IMIOError      *error)
{
    IMInt def_timeout = -1;
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);
    if (!timeout) timeout = &def_timeout;

    return __im_io_unix_wait_internal (unix_channel->fd,
                                       IO_UNIX_WAIT_READ,
                                       timeout,
                                       error);
}

static IMBool
__im_io_unix_read_with_timeout (IMIOChannel    *channel,
                                IMChar         *buf,
                                IMSize          count,
                                IMSize         *bytes_read,
                                IMInt          *timeout,
                                IMIOError      *error)
{
    int    ret;
    IMSize nbytes = 0;
    IMUInt old_flags;
    IMInt def_timeout = -1;

    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    if (count > SSIZE_MAX) count = SSIZE_MAX;
    if (bytes_read) *bytes_read = 0;
    if (!timeout) timeout = &def_timeout;

    if (*timeout < 0)
        return __im_io_channel_unix_read (channel, buf, count, bytes_read, error);

    if (!__im_io_channel_unix_get_flags (channel, &old_flags, error))
        return FALSE;

    // Set fd to non-block mode so that it won't be blocked.
    if ((old_flags & IM_IO_FLAG_NONBLOCK) == 0) {
        if (!__im_io_channel_unix_set_flags (channel,
                                             old_flags | IM_IO_FLAG_NONBLOCK,
                                             error))
            return FALSE;
    }

    while (count > 0) {
        if (!__im_io_unix_wait_internal (unix_channel->fd,
                                         IO_UNIX_WAIT_READ,
                                         timeout,
                                         error)) {
            ret = -1;
            break;
        }

        ret = read (unix_channel->fd, buf, count);

        if (ret >= 0) {
            buf += ret;
            nbytes += ret;
            count -= ret;
            if (*timeout != 0)
                continue;
            else
                break;
        }

#ifdef EINTR
        /* Interrupted? continue to try */
        if (errno == EINTR)
            continue;
#endif
        if (error)
            *error = im_io_channel_error_from_errno (errno);
        break;
    }
    if (bytes_read) *bytes_read = nbytes;

    if ((old_flags & IM_IO_FLAG_NONBLOCK) == 0) {
        if (!__im_io_channel_unix_set_flags (channel,
                                             old_flags,
                                             error))
            return FALSE;
    }

    return ret >= 0;
}

static IMBool
__im_io_unix_wait_for_writeable (IMIOChannel    *channel,
                                 IMInt          *timeout,
                                 IMIOError      *error)
{
    IMInt def_timeout = -1;
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);
    if (!timeout) timeout = &def_timeout;

    return __im_io_unix_wait_internal (unix_channel->fd,
                                       IO_UNIX_WAIT_WRITE,
                                       timeout,
                                       error);
}

static IMBool
__im_io_unix_write_with_timeout (IMIOChannel    *channel,
                                 const IMChar   *buf,
                                 IMSize          count,
                                 IMSize         *bytes_written,
                                 IMInt          *timeout,
                                 IMIOError      *error)
{
    int    ret;
    IMSize nbytes = 0;
    IMUInt old_flags;
    IMInt def_timeout = -1;
    void (*orig_sigpipe)(int);

    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);

    if (count > SSIZE_MAX) count = SSIZE_MAX;
    if (bytes_written) *bytes_written = 0;
    if (!timeout) timeout = &def_timeout;

    if (*timeout < 0)
        return __im_io_channel_unix_write (channel, buf, count, bytes_written, error);

    if (!__im_io_channel_unix_get_flags (channel, &old_flags, error))
        return FALSE;

    // Set fd to non-block mode so that it won't be blocked.
    if ((old_flags & IM_IO_FLAG_NONBLOCK) == 0) {
        if (!__im_io_channel_unix_set_flags (channel,
                                             old_flags | IM_IO_FLAG_NONBLOCK,
                                             error))
            return FALSE;
    }

    orig_sigpipe = signal (SIGPIPE, SIG_IGN);
    while (count > 0) {
        if (!__im_io_unix_wait_internal (unix_channel->fd,
                                         IO_UNIX_WAIT_WRITE,
                                         timeout,
                                         error)) {
            ret = -1;
            break;
        }

        ret = write (unix_channel->fd, buf, count);

        if (ret >= 0) {
            buf += ret;
            nbytes += ret;
            count -= ret;
            if (*timeout != 0)
                continue;
            else
                break;
        }

#ifdef EINTR
        /* Interrupted? continue to try */
        if (errno == EINTR)
            continue;
#endif
        if (error)
            *error = im_io_channel_error_from_errno (errno);
        break;
    }
    if (bytes_written) *bytes_written = nbytes;

    if (orig_sigpipe != SIG_ERR)
        signal (SIGPIPE, orig_sigpipe);
    else
        signal (SIGPIPE, SIG_DFL);

    if ((old_flags & IM_IO_FLAG_NONBLOCK) == 0) {
        if (!__im_io_channel_unix_set_flags (channel,
                                             old_flags,
                                             error))
            return FALSE;
    }

    return ret >= 0;
}

static void
__im_io_channel_unix_class_init (IMPointer klass)
{
    IMIOChannelClass *cp = (IMIOChannelClass *)klass;

    cp->valid       = __im_io_channel_unix_valid;
    cp->read        = __im_io_channel_unix_read;
    cp->write       = __im_io_channel_unix_write;
    cp->seek        = __im_io_channel_unix_seek;
    cp->tell        = __im_io_channel_unix_tell;
    cp->close       = __im_io_channel_unix_close;
    cp->mmap        = __im_io_channel_unix_mmap;
    cp->munmap      = __im_io_channel_unix_munmap;
    cp->set_flags   = __im_io_channel_unix_set_flags;
    cp->get_flags   = __im_io_channel_unix_get_flags;

    cp->wait_for_readable   = __im_io_unix_wait_for_readable;
    cp->read_with_timeout   = __im_io_unix_read_with_timeout;
    cp->wait_for_writeable  = __im_io_unix_wait_for_writeable;
    cp->write_with_timeout  = __im_io_unix_write_with_timeout;
}

static void
__im_io_channel_unix_initialize (IMPointer obj)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(obj);
    _im_assert (unix_channel);
    unix_channel->fd = -1;
    unix_channel->mmapped_pointers = 0;
}

static void
__im_io_channel_unix_finalize (IMPointer obj)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(obj);
    _im_assert (unix_channel);

    if (__im_io_channel_unix_valid (IM_IO_CHANNEL(obj)) &&
        im_io_channel_get_close_on_unref(IM_IO_CHANNEL(obj)))
        close (unix_channel->fd);
    if (unix_channel->mmapped_pointers)
        im_hash_table_destroy (unix_channel->mmapped_pointers);
}

void
__im_type_register_io_channel_unix ()
{
    static IMTypeInfo im_io_channel_unix_type_info = 
    {
        sizeof (IMIOChannelUnixClass),   /**< class_size >**/
        __im_io_channel_unix_class_init, /**< class_init >**/
        0,                               /**< class_finalize >**/

        sizeof (IMIOChannelUnix),        /**< instance_size >**/
        __im_io_channel_unix_initialize, /**< instance_init >**/
        __im_io_channel_unix_finalize,   /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_IO_CHANNEL,
                                      IM_TYPE_IO_CHANNEL_UNIX,
                                      "IOChannelUnix",
                                      &im_io_channel_unix_type_info,
                                      TRUE);
}

IMIOChannel*
im_io_channel_unix_new (int fd)
{
    IMIOChannel *channel;
    int fcntl_flags;

    _im_assert (fd >= 0);
    fcntl_flags = fcntl (fd, F_GETFL);
    _im_assert (fcntl_flags != -1);

    channel = (IMIOChannel*) im_object_new (IM_TYPE_IO_CHANNEL_UNIX);
    _im_assert (channel);

    channel->close_on_unref = FALSE;

    IM_IO_CHANNEL_UNIX(channel)->fd = fd;
    return channel;
}

IMInt
im_io_channel_unix_get_fd (IMIOChannel    *channel)
{
    IMIOChannelUnix *unix_channel = IM_IO_CHANNEL_UNIX(channel);
    _im_assert (unix_channel);
    return unix_channel->fd;
}

IMIOChannel*
im_io_channel_unix_new_file (const IMChar   *filename,
                             const IMChar   *mode,
                             IMIOError      *error)
{
    int fd, flags;
    mode_t create_mode;
    IMIOChannel *channel;
    enum {
        MODE_R = 1 << 0,
        MODE_W = 1 << 1,
        MODE_A = 1 << 2,
        MODE_PLUS = 1 << 3
    } mode_num;
    struct stat fstat_result;

    _im_return_val_if_fail (filename != 0, 0);
    _im_return_val_if_fail (mode != 0, 0);

    switch (mode[0]) {
        case 'r':
            mode_num = MODE_R;
            break;
        case 'w':
            mode_num = MODE_W;
            break;
        case 'a':
            mode_num = MODE_A;
            break;
        default:
            _im_warn ("Invalid IMIOChannel file mode %s.\n", mode);
            if (error) *error = IM_IO_ERROR_INVAL;
            return 0;
    }

    switch (mode[1]) {
        case '\0':
            break;
        case '+':
            if (mode[2] == '\0') {
                mode_num |= MODE_PLUS;
                break;
            }
            /* Fall through */
        default:
            _im_warn ("Invalid IMIOChannel file mode %s.\n", mode);
            if (error) *error = IM_IO_ERROR_INVAL;
            return 0;
    }

    switch (mode_num) {
        case MODE_R:
            flags = O_RDONLY;
            break;
        case MODE_W:
            flags = O_WRONLY | O_TRUNC | O_CREAT;
            break;
        case MODE_A:
            flags = O_WRONLY | O_APPEND | O_CREAT;
            break;
        case MODE_R | MODE_PLUS:
            flags = O_RDWR;
            break;
        case MODE_W | MODE_PLUS:
            flags = O_RDWR | O_TRUNC | O_CREAT;
            break;
        case MODE_A | MODE_PLUS:
            flags = O_RDWR | O_APPEND | O_CREAT;
            break;
        default:
            _im_assert_not_reached ("Invalid IMIOChannel file mode.\n");
            flags = 0;
    }

    create_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    fd = open (filename, flags, create_mode);
    if (fd == -1) {
        if (error) *error = im_io_channel_error_from_errno(errno);
        return 0;
    }

    /* Check again */
    if (fstat (fd, &fstat_result) == -1) {
        close (fd);
        if (error) *error = im_io_channel_error_from_errno(errno);
        return 0;
    }

    channel = (IMIOChannel *) im_object_new (IM_TYPE_IO_CHANNEL_UNIX);
    _im_assert (channel);

    channel->close_on_unref = TRUE;

    ((IMIOChannelUnix *) channel)->fd = fd;
    __im_fd_set_close_on_exec (fd);
    return channel;
}


/*
vi:ts=4:nowrap:ai:expandtab
*/
